package org.andengine.util.modifier;

import org.andengine.util.modifier.IModifier.IModifierListener;
import org.andengine.util.modifier.util.ModifierUtils;


/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 19:39:25 - 19.03.2010
 */
public class SequenceModifier<T> extends BaseModifier<T> implements IModifierListener<T> {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final IModifier<T>[] mSubSequenceModifiers;
    private final float mDuration;
    private ISubSequenceModifierListener<T> mSubSequenceModifierListener;
    private int mCurrentSubSequenceModifierIndex;
    private float mSecondsElapsed;
    private boolean mFinishedCached;

    // ===========================================================
    // Constructors
    // ===========================================================

    public SequenceModifier(final IModifier<T>... pModifiers) throws IllegalArgumentException {
        this(null, null, pModifiers);
    }

    public SequenceModifier(final ISubSequenceModifierListener<T> pSubSequenceModifierListener, final IModifier<T>... pModifiers) throws IllegalArgumentException {
        this(pSubSequenceModifierListener, null, pModifiers);
    }

    public SequenceModifier(final IModifierListener<T> pModifierListener, final IModifier<T>... pModifiers) throws IllegalArgumentException {
        this(null, pModifierListener, pModifiers);
    }

    public SequenceModifier(final ISubSequenceModifierListener<T> pSubSequenceModifierListener, final IModifierListener<T> pModifierListener, final IModifier<T>... pModifiers) throws IllegalArgumentException {
        super(pModifierListener);

        if (pModifiers.length == 0) {
            throw new IllegalArgumentException("pModifiers must not be empty!");
        }

        this.assertNoNullModifier(pModifiers);

        this.mSubSequenceModifierListener = pSubSequenceModifierListener;
        this.mSubSequenceModifiers = pModifiers;

        this.mDuration = ModifierUtils.getSequenceDurationOfModifier(pModifiers);

        pModifiers[0].addModifierListener(this);
    }

    @SuppressWarnings("unchecked")
    protected SequenceModifier(final SequenceModifier<T> pSequenceModifier) throws DeepCopyNotSupportedException {
        this.mDuration = pSequenceModifier.mDuration;

        final IModifier<T>[] otherModifiers = pSequenceModifier.mSubSequenceModifiers;
        this.mSubSequenceModifiers = new IModifier[otherModifiers.length];

        final IModifier<T>[] subSequenceModifiers = this.mSubSequenceModifiers;
        for (int i = subSequenceModifiers.length - 1; i >= 0; i--) {
            subSequenceModifiers[i] = otherModifiers[i].deepCopy();
        }

        subSequenceModifiers[0].addModifierListener(this);
    }

    @Override
    public SequenceModifier<T> deepCopy() throws DeepCopyNotSupportedException {
        return new SequenceModifier<T>(this);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public ISubSequenceModifierListener<T> getSubSequenceModifierListener() {
        return this.mSubSequenceModifierListener;
    }

    public void setSubSequenceModifierListener(final ISubSequenceModifierListener<T> pSubSequenceModifierListener) {
        this.mSubSequenceModifierListener = pSubSequenceModifierListener;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public float getSecondsElapsed() {
        return this.mSecondsElapsed;
    }

    @Override
    public float getDuration() {
        return this.mDuration;
    }

    @Override
    public float onUpdate(final float pSecondsElapsed, final T pItem) {
        if (this.mFinished) {
            return 0;
        } else {
            float secondsElapsedRemaining = pSecondsElapsed;
            this.mFinishedCached = false;
            while (secondsElapsedRemaining > 0 && !this.mFinishedCached) {
                secondsElapsedRemaining -= this.mSubSequenceModifiers[this.mCurrentSubSequenceModifierIndex].onUpdate(secondsElapsedRemaining, pItem);
            }
            this.mFinishedCached = false;

            final float secondsElapsedUsed = pSecondsElapsed - secondsElapsedRemaining;
            this.mSecondsElapsed += secondsElapsedUsed;
            return secondsElapsedUsed;
        }
    }

    @Override
    public void reset() {
        if (this.isFinished()) {
            this.mSubSequenceModifiers[this.mSubSequenceModifiers.length - 1].removeModifierListener(this);
        } else {
            this.mSubSequenceModifiers[this.mCurrentSubSequenceModifierIndex].removeModifierListener(this);
        }

        this.mCurrentSubSequenceModifierIndex = 0;
        this.mFinished = false;
        this.mSecondsElapsed = 0;

        this.mSubSequenceModifiers[0].addModifierListener(this);

        final IModifier<T>[] subSequenceModifiers = this.mSubSequenceModifiers;
        for (int i = subSequenceModifiers.length - 1; i >= 0; i--) {
            subSequenceModifiers[i].reset();
        }
    }

    @Override
    public void onModifierStarted(final IModifier<T> pModifier, final T pItem) {
        if (this.mCurrentSubSequenceModifierIndex == 0) {
            this.onModifierStarted(pItem);
        }

        if (this.mSubSequenceModifierListener != null) {
            this.mSubSequenceModifierListener.onSubSequenceStarted(pModifier, pItem, this.mCurrentSubSequenceModifierIndex);
        }
    }

    @Override
    public void onModifierFinished(final IModifier<T> pModifier, final T pItem) {
        if (this.mSubSequenceModifierListener != null) {
            this.mSubSequenceModifierListener.onSubSequenceFinished(pModifier, pItem, this.mCurrentSubSequenceModifierIndex);
        }
        pModifier.removeModifierListener(this);

        this.mCurrentSubSequenceModifierIndex++;

        if (this.mCurrentSubSequenceModifierIndex < this.mSubSequenceModifiers.length) {
            final IModifier<T> nextSubSequenceModifier = this.mSubSequenceModifiers[this.mCurrentSubSequenceModifierIndex];
            nextSubSequenceModifier.addModifierListener(this);
        } else {
            this.mFinished = true;
            this.mFinishedCached = true;

            this.onModifierFinished(pItem);
        }
    }

    // ===========================================================
    // Methods
    // ===========================================================

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public interface ISubSequenceModifierListener<T> {
        public void onSubSequenceStarted(final IModifier<T> pModifier, final T pItem, final int pIndex);

        public void onSubSequenceFinished(final IModifier<T> pModifier, final T pItem, final int pIndex);
    }
}
